/*
 * lcp.c - PPP Link Control Protocol.
 *
 * Copyright (c) 1984-2000 Carnegie Mellon University. All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 *
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 *
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in
 *    the documentation and/or other materials provided with the
 *    distribution.
 *
 * 3. The name "Carnegie Mellon University" must not be used to
 *    endorse or promote products derived from this software without
 *    prior written permission. For permission or any legal
 *    details, please contact
 *      Office of Technology Transfer
 *      Carnegie Mellon University
 *      5000 Forbes Avenue
 *      Pittsburgh, PA  15213-3890
 *      (412) 268-4387, fax: (412) 268-7395
 *      tech-transfer@andrew.cmu.edu
 *
 * 4. Redistributions of any form whatsoever must retain the following
 *    acknowledgment:
 *    "This product includes software developed by Computing Services
 *     at Carnegie Mellon University (http://www.cmu.edu/computing/)."
 *
 * CARNEGIE MELLON UNIVERSITY DISCLAIMS ALL WARRANTIES WITH REGARD TO
 * THIS SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
 * AND FITNESS, IN NO EVENT SHALL CARNEGIE MELLON UNIVERSITY BE LIABLE
 * FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN
 * AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING
 * OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
 */

#include "netif/ppp/ppp_opts.h"
#if PPP_SUPPORT /* don't build if not configured for use in lwipopts.h */

/*
 * @todo:
 */

#if 0 /* UNUSED */
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#endif /* UNUSED */

#include "netif/ppp/ppp_impl.h"

#include "netif/ppp/fsm.h"
#include "netif/ppp/lcp.h"
#if CHAP_SUPPORT
#include "netif/ppp/chap-new.h"
#endif /* CHAP_SUPPORT */
#include "netif/ppp/magic.h"

/*
 * When the link comes up we want to be able to wait for a short while,
 * or until seeing some input from the peer, before starting to send
 * configure-requests.  We do this by delaying the fsm_lowerup call.
 */
/* steal a bit in fsm flags word */
#define DELAYED_UP 0x80

static void lcp_delayed_up(void *arg);

/*
 * LCP-related command-line options.
 */
#if 0  /* UNUSED */
int	lcp_echo_interval = 0; 	/* Interval between LCP echo-requests */
int	lcp_echo_fails = 0;	/* Tolerance to unanswered echo-requests */
#endif /* UNUSED */

#if 0  /* UNUSED */
/* options */
static u_int lcp_echo_interval      = LCP_ECHOINTERVAL; /* Interval between LCP echo-requests */
static u_int lcp_echo_fails         = LCP_MAXECHOFAILS; /* Tolerance to unanswered echo-requests */
#endif /* UNUSED */

#if 0 /* UNUSED */
#if PPP_LCP_ADAPTIVE
bool	lcp_echo_adaptive = 0;	/* request echo only if the link was idle */
#endif
bool	lax_recv = 0;		/* accept control chars in asyncmap */
bool	noendpoint = 0;		/* don't send/accept endpoint discriminator */
#endif /* UNUSED */

#if PPP_OPTIONS
static int noopt(char **);
#endif /* PPP_OPTIONS */

#ifdef HAVE_MULTILINK
static int setendpoint(char **);
static void printendpoint(option_t *, void (*)(void *, char *, ...), void *);
#endif /* HAVE_MULTILINK */

#if PPP_OPTIONS
static option_t lcp_option_list[] = {
    /* LCP options */
    {"-all", o_special_noarg, (void *)noopt,
     "Don't request/allow any LCP options"},

    {"noaccomp", o_bool, &lcp_wantoptions[0].neg_accompression,
     "Disable address/control compression", OPT_A2CLR,
     &lcp_allowoptions[0].neg_accompression},
    {"-ac", o_bool, &lcp_wantoptions[0].neg_accompression,
     "Disable address/control compression", OPT_ALIAS | OPT_A2CLR,
     &lcp_allowoptions[0].neg_accompression},

    {"asyncmap", o_uint32, &lcp_wantoptions[0].asyncmap,
     "Set asyncmap (for received packets)", OPT_OR,
     &lcp_wantoptions[0].neg_asyncmap},
    {"-as", o_uint32, &lcp_wantoptions[0].asyncmap,
     "Set asyncmap (for received packets)", OPT_ALIAS | OPT_OR,
     &lcp_wantoptions[0].neg_asyncmap},
    {"default-asyncmap", o_uint32, &lcp_wantoptions[0].asyncmap,
     "Disable asyncmap negotiation",
     OPT_OR | OPT_NOARG | OPT_VAL(~0U) | OPT_A2CLR,
     &lcp_allowoptions[0].neg_asyncmap},
    {"-am", o_uint32, &lcp_wantoptions[0].asyncmap,
     "Disable asyncmap negotiation",
     OPT_ALIAS | OPT_OR | OPT_NOARG | OPT_VAL(~0U) | OPT_A2CLR,
     &lcp_allowoptions[0].neg_asyncmap},

    {"nomagic", o_bool, &lcp_wantoptions[0].neg_magicnumber,
     "Disable magic number negotiation (looped-back line detection)", OPT_A2CLR,
     &lcp_allowoptions[0].neg_magicnumber},
    {"-mn", o_bool, &lcp_wantoptions[0].neg_magicnumber,
     "Disable magic number negotiation (looped-back line detection)",
     OPT_ALIAS | OPT_A2CLR, &lcp_allowoptions[0].neg_magicnumber},

    {"mru", o_int, &lcp_wantoptions[0].mru,
     "Set MRU (maximum received packet size) for negotiation", OPT_PRIO,
     &lcp_wantoptions[0].neg_mru},
    {"default-mru", o_bool, &lcp_wantoptions[0].neg_mru,
     "Disable MRU negotiation (use default 1500)", OPT_PRIOSUB | OPT_A2CLR,
     &lcp_allowoptions[0].neg_mru},
    {"-mru", o_bool, &lcp_wantoptions[0].neg_mru,
     "Disable MRU negotiation (use default 1500)",
     OPT_ALIAS | OPT_PRIOSUB | OPT_A2CLR, &lcp_allowoptions[0].neg_mru},

    {"mtu", o_int, &lcp_allowoptions[0].mru, "Set our MTU", OPT_LIMITS, NULL,
     MAXMRU, MINMRU},

    {"nopcomp", o_bool, &lcp_wantoptions[0].neg_pcompression,
     "Disable protocol field compression", OPT_A2CLR,
     &lcp_allowoptions[0].neg_pcompression},
    {"-pc", o_bool, &lcp_wantoptions[0].neg_pcompression,
     "Disable protocol field compression", OPT_ALIAS | OPT_A2CLR,
     &lcp_allowoptions[0].neg_pcompression},

    {"passive", o_bool, &lcp_wantoptions[0].passive, "Set passive mode", 1},
    {"-p", o_bool, &lcp_wantoptions[0].passive, "Set passive mode",
     OPT_ALIAS | 1},

    {"silent", o_bool, &lcp_wantoptions[0].silent, "Set silent mode", 1},

    {"lcp-echo-failure", o_int, &lcp_echo_fails,
     "Set number of consecutive echo failures to indicate link failure",
     OPT_PRIO},
    {"lcp-echo-interval", o_int, &lcp_echo_interval,
     "Set time in seconds between LCP echo requests", OPT_PRIO},
#if PPP_LCP_ADAPTIVE
    {"lcp-echo-adaptive", o_bool, &lcp_echo_adaptive,
     "Suppress LCP echo requests if traffic was received", 1},
#endif
    {"lcp-restart", o_int, &lcp_fsm[0].timeouttime,
     "Set time in seconds between LCP retransmissions", OPT_PRIO},
    {"lcp-max-terminate", o_int, &lcp_fsm[0].maxtermtransmits,
     "Set maximum number of LCP terminate-request transmissions", OPT_PRIO},
    {"lcp-max-configure", o_int, &lcp_fsm[0].maxconfreqtransmits,
     "Set maximum number of LCP configure-request transmissions", OPT_PRIO},
    {"lcp-max-failure", o_int, &lcp_fsm[0].maxnakloops,
     "Set limit on number of LCP configure-naks", OPT_PRIO},

    {"receive-all", o_bool, &lax_recv, "Accept all received control characters",
     1},

#ifdef HAVE_MULTILINK
    {"mrru", o_int, &lcp_wantoptions[0].mrru,
     "Maximum received packet size for multilink bundle", OPT_PRIO,
     &lcp_wantoptions[0].neg_mrru},

    {"mpshortseq", o_bool, &lcp_wantoptions[0].neg_ssnhf,
     "Use short sequence numbers in multilink headers", OPT_PRIO | 1,
     &lcp_allowoptions[0].neg_ssnhf},
    {"nompshortseq", o_bool, &lcp_wantoptions[0].neg_ssnhf,
     "Don't use short sequence numbers in multilink headers",
     OPT_PRIOSUB | OPT_A2CLR, &lcp_allowoptions[0].neg_ssnhf},

    {"endpoint", o_special, (void *)setendpoint,
     "Endpoint discriminator for multilink", OPT_PRIO | OPT_A2PRINTER,
     (void *)printendpoint},
#endif /* HAVE_MULTILINK */

    {"noendpoint", o_bool, &noendpoint,
     "Don't send or accept multilink endpoint discriminator", 1},

    {NULL}};
#endif /* PPP_OPTIONS */

/*
 * Callbacks for fsm code.  (CI = Configuration Information)
 */
static void lcp_resetci(fsm *f); /* Reset our CI */
static int lcp_cilen(fsm *f);    /* Return length of our CI */
static void lcp_addci(fsm *f, u_char *ucp, int *lenp); /* Add our CI to pkt */
static int lcp_ackci(fsm *f, u_char *p, int len);      /* Peer ack'd our CI */
static int lcp_nakci(fsm *f, u_char *p, int len,
                     int treat_as_reject);        /* Peer nak'd our CI */
static int lcp_rejci(fsm *f, u_char *p, int len); /* Peer rej'd our CI */
static int lcp_reqci(fsm *f, u_char *inp, int *lenp,
                     int reject_if_disagree); /* Rcv peer CI */
static void lcp_up(fsm *f);                   /* We're UP */
static void lcp_down(fsm *f);                 /* We're DOWN */
static void lcp_starting(fsm *);              /* We need lower layer up */
static void lcp_finished(fsm *);              /* We need lower layer down */
static int lcp_extcode(fsm *f, int code, int id, u_char *inp, int len);
static void lcp_rprotrej(fsm *f, u_char *inp, int len);

/*
 * routines to send LCP echos to peer
 */

static void lcp_echo_lowerup(ppp_pcb *pcb);
static void lcp_echo_lowerdown(ppp_pcb *pcb);
static void LcpEchoTimeout(void *arg);
static void lcp_received_echo_reply(fsm *f, int id, u_char *inp, int len);
static void LcpSendEchoRequest(fsm *f);
static void LcpLinkFailure(fsm *f);
static void LcpEchoCheck(fsm *f);

static const fsm_callbacks lcp_callbacks = {
    /* LCP callback routines */
    lcp_resetci,  /* Reset our Configuration Information */
    lcp_cilen,    /* Length of our Configuration Information */
    lcp_addci,    /* Add our Configuration Information */
    lcp_ackci,    /* ACK our Configuration Information */
    lcp_nakci,    /* NAK our Configuration Information */
    lcp_rejci,    /* Reject our Configuration Information */
    lcp_reqci,    /* Request peer's Configuration Information */
    lcp_up,       /* Called when fsm reaches OPENED state */
    lcp_down,     /* Called when fsm leaves OPENED state */
    lcp_starting, /* Called when we want the lower layer up */
    lcp_finished, /* Called when we want the lower layer down */
    NULL,         /* Called when Protocol-Reject received */
    NULL,         /* Retransmission is necessary */
    lcp_extcode,  /* Called to handle LCP-specific codes */
    "LCP"         /* String name of protocol */
};

/*
 * Protocol entry points.
 * Some of these are called directly.
 */

static void lcp_init(ppp_pcb *pcb);
static void lcp_input(ppp_pcb *pcb, u_char *p, int len);
static void lcp_protrej(ppp_pcb *pcb);
#if PRINTPKT_SUPPORT
static int lcp_printpkt(const u_char *p, int plen,
                        void (*printer)(void *, const char *, ...), void *arg);
#endif /* PRINTPKT_SUPPORT */

const struct protent lcp_protent = {PPP_LCP,
                                    lcp_init,
                                    lcp_input,
                                    lcp_protrej,
                                    lcp_lowerup,
                                    lcp_lowerdown,
                                    lcp_open,
                                    lcp_close,
#if PRINTPKT_SUPPORT
                                    lcp_printpkt,
#endif /* PRINTPKT_SUPPORT */
#if PPP_DATAINPUT
                                    NULL,
#endif /* PPP_DATAINPUT */
#if PRINTPKT_SUPPORT
                                    "LCP",
                                    NULL,
#endif /* PRINTPKT_SUPPORT */
#if PPP_OPTIONS
                                    lcp_option_list,
                                    NULL,
#endif /* PPP_OPTIONS */
#if DEMAND_SUPPORT
                                    NULL,
                                    NULL
#endif /* DEMAND_SUPPORT */
};

/*
 * Length of each type of configuration option (in octets)
 */
#define CILEN_VOID 2
#define CILEN_CHAR 3
#define CILEN_SHORT 4 /* CILEN_VOID + 2 */
#if CHAP_SUPPORT
#define CILEN_CHAP 5 /* CILEN_VOID + 2 + 1 */
#endif               /* CHAP_SUPPORT */
#define CILEN_LONG 6 /* CILEN_VOID + 4 */
#if LQR_SUPPORT
#define CILEN_LQR 8 /* CILEN_VOID + 2 + 4 */
#endif              /* LQR_SUPPORT */
#define CILEN_CBCP 3

#define CODENAME(x) ((x) == CONFACK ? "ACK" : (x) == CONFNAK ? "NAK" : "REJ")

#if PPP_OPTIONS
/*
 * noopt - Disable all options (why?).
 */
static int noopt(argv)
char **argv;
{
  BZERO((char *)&lcp_wantoptions[0], sizeof(struct lcp_options));
  BZERO((char *)&lcp_allowoptions[0], sizeof(struct lcp_options));

  return (1);
}
#endif /* PPP_OPTIONS */

#ifdef HAVE_MULTILINK
static int setendpoint(argv)
char **argv;
{
  if (str_to_epdisc(&lcp_wantoptions[0].endpoint, *argv)) {
    lcp_wantoptions[0].neg_endpoint = 1;
    return 1;
  }
  option_error("Can't parse '%s' as an endpoint discriminator", *argv);
  return 0;
}

static void printendpoint(opt, printer, arg) option_t *opt;
void (*printer)(void *, char *, ...);
void *arg;
{ printer(arg, "%s", epdisc_to_str(&lcp_wantoptions[0].endpoint)); }
#endif /* HAVE_MULTILINK */

/*
 * lcp_init - Initialize LCP.
 */
static void lcp_init(ppp_pcb *pcb) {
  fsm *f = &pcb->lcp_fsm;
  lcp_options *wo = &pcb->lcp_wantoptions;
  lcp_options *ao = &pcb->lcp_allowoptions;

  f->pcb = pcb;
  f->protocol = PPP_LCP;
  f->callbacks = &lcp_callbacks;

  fsm_init(f);

  BZERO(wo, sizeof(*wo));
  wo->neg_mru = 1;
  wo->mru = PPP_MRU;
  wo->neg_asyncmap = 1;
  wo->neg_magicnumber = 1;
  wo->neg_pcompression = 1;
  wo->neg_accompression = 1;

  BZERO(ao, sizeof(*ao));
  ao->neg_mru = 1;
  ao->mru = PPP_MAXMRU;
  ao->neg_asyncmap = 1;
#if CHAP_SUPPORT
  ao->neg_chap = 1;
  ao->chap_mdtype = CHAP_MDTYPE_SUPPORTED;
#endif /* CHAP_SUPPORT */
#if PAP_SUPPORT
  ao->neg_upap = 1;
#endif /* PAP_SUPPORT */
#if EAP_SUPPORT
  ao->neg_eap = 1;
#endif /* EAP_SUPPORT */
  ao->neg_magicnumber = 1;
  ao->neg_pcompression = 1;
  ao->neg_accompression = 1;
  ao->neg_endpoint = 1;
}

/*
 * lcp_open - LCP is allowed to come up.
 */
void lcp_open(ppp_pcb *pcb) {
  fsm *f = &pcb->lcp_fsm;
  lcp_options *wo = &pcb->lcp_wantoptions;

  f->flags &= ~(OPT_PASSIVE | OPT_SILENT);
  if (wo->passive)
    f->flags |= OPT_PASSIVE;
  if (wo->silent)
    f->flags |= OPT_SILENT;
  fsm_open(f);
}

/*
 * lcp_close - Take LCP down.
 */
void lcp_close(ppp_pcb *pcb, const char *reason) {
  fsm *f = &pcb->lcp_fsm;
  int oldstate;

  if (pcb->phase != PPP_PHASE_DEAD
#ifdef HAVE_MULTILINK
      && pcb->phase != PPP_PHASE_MASTER
#endif /* HAVE_MULTILINK */
  )
    new_phase(pcb, PPP_PHASE_TERMINATE);

  if (f->flags & DELAYED_UP) {
    UNTIMEOUT(lcp_delayed_up, f);
    f->state = PPP_FSM_STOPPED;
  }
  oldstate = f->state;

  fsm_close(f, reason);
  if (oldstate == PPP_FSM_STOPPED &&
      (f->flags & (OPT_PASSIVE | OPT_SILENT | DELAYED_UP))) {
    /*
     * This action is not strictly according to the FSM in RFC1548,
     * but it does mean that the program terminates if you do a
     * lcp_close() when a connection hasn't been established
     * because we are in passive/silent mode or because we have
     * delayed the fsm_lowerup() call and it hasn't happened yet.
     */
    f->flags &= ~DELAYED_UP;
    lcp_finished(f);
  }
}

/*
 * lcp_lowerup - The lower layer is up.
 */
void lcp_lowerup(ppp_pcb *pcb) {
  lcp_options *wo = &pcb->lcp_wantoptions;
  fsm *f = &pcb->lcp_fsm;
  /*
   * Don't use A/C or protocol compression on transmission,
   * but accept A/C and protocol compressed packets
   * if we are going to ask for A/C and protocol compression.
   */
  if (ppp_send_config(pcb, PPP_DEFMRU, 0xffffffff, 0, 0) < 0 ||
      ppp_recv_config(pcb, PPP_DEFMRU,
                      (pcb->settings.lax_recv ? 0 : 0xffffffff),
                      wo->neg_pcompression, wo->neg_accompression) < 0)
    return;
  pcb->peer_mru = PPP_DEFMRU;

  if (pcb->settings.listen_time != 0) {
    f->flags |= DELAYED_UP;
    TIMEOUTMS(lcp_delayed_up, f, pcb->settings.listen_time);
  } else
    fsm_lowerup(f);
}

/*
 * lcp_lowerdown - The lower layer is down.
 */
void lcp_lowerdown(ppp_pcb *pcb) {
  fsm *f = &pcb->lcp_fsm;

  if (f->flags & DELAYED_UP) {
    f->flags &= ~DELAYED_UP;
    UNTIMEOUT(lcp_delayed_up, f);
  } else
    fsm_lowerdown(f);
}

/*
 * lcp_delayed_up - Bring the lower layer up now.
 */
static void lcp_delayed_up(void *arg) {
  fsm *f = (fsm *)arg;

  if (f->flags & DELAYED_UP) {
    f->flags &= ~DELAYED_UP;
    fsm_lowerup(f);
  }
}

/*
 * lcp_input - Input LCP packet.
 */
static void lcp_input(ppp_pcb *pcb, u_char *p, int len) {
  fsm *f = &pcb->lcp_fsm;

  if (f->flags & DELAYED_UP) {
    f->flags &= ~DELAYED_UP;
    UNTIMEOUT(lcp_delayed_up, f);
    fsm_lowerup(f);
  }
  fsm_input(f, p, len);
}

/*
 * lcp_extcode - Handle a LCP-specific code.
 */
static int lcp_extcode(fsm *f, int code, int id, u_char *inp, int len) {
  ppp_pcb *pcb = f->pcb;
  lcp_options *go = &pcb->lcp_gotoptions;
  u_char *magp;

  switch (code) {
  case PROTREJ:
    lcp_rprotrej(f, inp, len);
    break;

  case ECHOREQ:
    if (f->state != PPP_FSM_OPENED)
      break;
    magp = inp;
    PUTLONG(go->magicnumber, magp);
    fsm_sdata(f, ECHOREP, id, inp, len);
    break;

  case ECHOREP:
    lcp_received_echo_reply(f, id, inp, len);
    break;

  case DISCREQ:
  case IDENTIF:
  case TIMEREM:
    break;

  default:
    return 0;
  }
  return 1;
}

/*
 * lcp_rprotrej - Receive an Protocol-Reject.
 *
 * Figure out which protocol is rejected and inform it.
 */
static void lcp_rprotrej(fsm *f, u_char *inp, int len) {
  int i;
  const struct protent *protp;
  u_short prot;
#if PPP_PROTOCOLNAME
  const char *pname;
#endif /* PPP_PROTOCOLNAME */

  if (len < 2) {
    LCPDEBUG(("lcp_rprotrej: Rcvd short Protocol-Reject packet!"));
    return;
  }

  GETSHORT(prot, inp);

  /*
   * Protocol-Reject packets received in any state other than the LCP
   * OPENED state SHOULD be silently discarded.
   */
  if (f->state != PPP_FSM_OPENED) {
    LCPDEBUG(("Protocol-Reject discarded: LCP in state %d", f->state));
    return;
  }

#if PPP_PROTOCOLNAME
  pname = protocol_name(prot);
#endif /* PPP_PROTOCOLNAME */

  /*
   * Upcall the proper Protocol-Reject routine.
   */
  for (i = 0; (protp = protocols[i]) != NULL; ++i)
    if (protp->protocol == prot) {
#if PPP_PROTOCOLNAME
      if (pname != NULL)
        ppp_dbglog(("Protocol-Reject for '%s' (0x%x) received", pname, prot));
      else
#endif /* PPP_PROTOCOLNAME */
        ppp_dbglog(("Protocol-Reject for 0x%x received", prot));
      (*protp->protrej)(f->pcb);
      return;
    }

#if PPP_PROTOCOLNAME
  if (pname != NULL)
    ppp_warn(
        ("Protocol-Reject for unsupported protocol '%s' (0x%x)", pname, prot));
  else
#endif /* #if PPP_PROTOCOLNAME */
    ppp_warn(("Protocol-Reject for unsupported protocol 0x%x", prot));
}

/*
 * lcp_protrej - A Protocol-Reject was received.
 */
/*ARGSUSED*/
static void lcp_protrej(ppp_pcb *pcb) {
  /*
   * Can't reject LCP!
   */
  ppp_error(("Received Protocol-Reject for LCP!"));
  fsm_protreject(&pcb->lcp_fsm);
}

/*
 * lcp_sprotrej - Send a Protocol-Reject for some protocol.
 */
void lcp_sprotrej(ppp_pcb *pcb, u_char *p, int len) {
  fsm *f = &pcb->lcp_fsm;
  /*
   * Send back the protocol and the information field of the
   * rejected packet.  We only get here if LCP is in the OPENED state.
   */
#if 0
    p += 2;
    len -= 2;
#endif

  fsm_sdata(f, PROTREJ, ++f->id, p, len);
}

/*
 * lcp_resetci - Reset our CI.
 */
static void lcp_resetci(fsm *f) {
  ppp_pcb *pcb = f->pcb;
  lcp_options *wo = &pcb->lcp_wantoptions;
  lcp_options *go = &pcb->lcp_gotoptions;
  lcp_options *ao = &pcb->lcp_allowoptions;

#if PPP_AUTH_SUPPORT

  /* note: default value is true for allow options */
  if (pcb->settings.user && pcb->settings.passwd) {
#if PAP_SUPPORT
    if (pcb->settings.refuse_pap) {
      ao->neg_upap = 0;
    }
#endif /* PAP_SUPPORT */
#if CHAP_SUPPORT
    if (pcb->settings.refuse_chap) {
      ao->chap_mdtype &= ~MDTYPE_MD5;
    }
#if MSCHAP_SUPPORT
    if (pcb->settings.refuse_mschap) {
      ao->chap_mdtype &= ~MDTYPE_MICROSOFT;
    }
    if (pcb->settings.refuse_mschap_v2) {
      ao->chap_mdtype &= ~MDTYPE_MICROSOFT_V2;
    }
#endif /* MSCHAP_SUPPORT */
    ao->neg_chap = (ao->chap_mdtype != MDTYPE_NONE);
#endif /* CHAP_SUPPORT */
#if EAP_SUPPORT
    if (pcb->settings.refuse_eap) {
      ao->neg_eap = 0;
    }
#endif /* EAP_SUPPORT */

#if PPP_SERVER
    /* note: default value is false for wanted options */
    if (pcb->settings.auth_required) {
#if PAP_SUPPORT
      if (!pcb->settings.refuse_pap) {
        wo->neg_upap = 1;
      }
#endif /* PAP_SUPPORT */
#if CHAP_SUPPORT
      if (!pcb->settings.refuse_chap) {
        wo->chap_mdtype |= MDTYPE_MD5;
      }
#if MSCHAP_SUPPORT
      if (!pcb->settings.refuse_mschap) {
        wo->chap_mdtype |= MDTYPE_MICROSOFT;
      }
      if (!pcb->settings.refuse_mschap_v2) {
        wo->chap_mdtype |= MDTYPE_MICROSOFT_V2;
      }
#endif /* MSCHAP_SUPPORT */
      wo->neg_chap = (wo->chap_mdtype != MDTYPE_NONE);
#endif /* CHAP_SUPPORT */
#if EAP_SUPPORT
      if (!pcb->settings.refuse_eap) {
        wo->neg_eap = 1;
      }
#endif /* EAP_SUPPORT */
    }
#endif /* PPP_SERVER */

  } else {
#if PAP_SUPPORT
    ao->neg_upap = 0;
#endif /* PAP_SUPPORT */
#if CHAP_SUPPORT
    ao->neg_chap = 0;
    ao->chap_mdtype = MDTYPE_NONE;
#endif /* CHAP_SUPPORT */
#if EAP_SUPPORT
    ao->neg_eap = 0;
#endif /* EAP_SUPPORT */
  }

  PPPDEBUG(LOG_DEBUG, ("ppp: auth protocols:"));
#if PAP_SUPPORT
  PPPDEBUG(LOG_DEBUG, (" PAP=%d", ao->neg_upap));
#endif /* PAP_SUPPORT */
#if CHAP_SUPPORT
  PPPDEBUG(LOG_DEBUG, (" CHAP=%d CHAP_MD5=%d", ao->neg_chap,
                       !!(ao->chap_mdtype & MDTYPE_MD5)));
#if MSCHAP_SUPPORT
  PPPDEBUG(LOG_DEBUG,
           (" CHAP_MS=%d CHAP_MS2=%d", !!(ao->chap_mdtype & MDTYPE_MICROSOFT),
            !!(ao->chap_mdtype & MDTYPE_MICROSOFT_V2)));
#endif /* MSCHAP_SUPPORT */
#endif /* CHAP_SUPPORT */
#if EAP_SUPPORT
  PPPDEBUG(LOG_DEBUG, (" EAP=%d", ao->neg_eap));
#endif /* EAP_SUPPORT */
  PPPDEBUG(LOG_DEBUG, ("\n"));

#endif /* PPP_AUTH_SUPPORT */

  wo->magicnumber = magic();
  wo->numloops = 0;
  *go = *wo;
#ifdef HAVE_MULTILINK
  if (!multilink) {
    go->neg_mrru = 0;
#endif /* HAVE_MULTILINK */
    go->neg_ssnhf = 0;
    go->neg_endpoint = 0;
#ifdef HAVE_MULTILINK
  }
#endif /* HAVE_MULTILINK */
  if (pcb->settings.noendpoint)
    ao->neg_endpoint = 0;
  pcb->peer_mru = PPP_DEFMRU;
#if 0  /* UNUSED */
    auth_reset(pcb);
#endif /* UNUSED */
}

/*
 * lcp_cilen - Return length of our CI.
 */
static int lcp_cilen(fsm *f) {
  ppp_pcb *pcb = f->pcb;
  lcp_options *go = &pcb->lcp_gotoptions;

#define LENCIVOID(neg) ((neg) ? CILEN_VOID : 0)
#if CHAP_SUPPORT
#define LENCICHAP(neg) ((neg) ? CILEN_CHAP : 0)
#endif /* CHAP_SUPPORT */
#define LENCISHORT(neg) ((neg) ? CILEN_SHORT : 0)
#define LENCILONG(neg) ((neg) ? CILEN_LONG : 0)
#if LQR_SUPPORT
#define LENCILQR(neg) ((neg) ? CILEN_LQR : 0)
#endif /* LQR_SUPPORT */
#define LENCICBCP(neg) ((neg) ? CILEN_CBCP : 0)
  /*
   * NB: we only ask for one of CHAP, UPAP, or EAP, even if we will
   * accept more than one.  We prefer EAP first, then CHAP, then
   * PAP.
   */
  return (LENCISHORT(go->neg_mru && go->mru != PPP_DEFMRU) +
          LENCILONG(go->neg_asyncmap && go->asyncmap != 0xFFFFFFFF) +
#if EAP_SUPPORT
          LENCISHORT(go->neg_eap) +
#endif           /* EAP_SUPPORT */
#if CHAP_SUPPORT /* cannot be improved, embedding a directive within macro     \
                    arguments is not portable */
#if EAP_SUPPORT
          LENCICHAP(!go->neg_eap && go->neg_chap) +
#endif /* EAP_SUPPORT */
#if !EAP_SUPPORT
          LENCICHAP(go->neg_chap) +
#endif          /* !EAP_SUPPORT */
#endif          /* CHAP_SUPPORT */
#if PAP_SUPPORT /* cannot be improved, embedding a directive within macro      \
                   arguments is not portable */
#if EAP_SUPPORT && CHAP_SUPPORT
          LENCISHORT(!go->neg_eap && !go->neg_chap && go->neg_upap) +
#endif /* EAP_SUPPORT && CHAP_SUPPORT */
#if EAP_SUPPORT && !CHAP_SUPPORT
          LENCISHORT(!go->neg_eap && go->neg_upap) +
#endif /* EAP_SUPPORT && !CHAP_SUPPORT */
#if !EAP_SUPPORT && CHAP_SUPPORT
          LENCISHORT(!go->neg_chap && go->neg_upap) +
#endif /* !EAP_SUPPORT && CHAP_SUPPORT */
#if !EAP_SUPPORT && !CHAP_SUPPORT
          LENCISHORT(go->neg_upap) +
#endif /* !EAP_SUPPORT && !CHAP_SUPPORT */
#endif /* PAP_SUPPORT */
#if LQR_SUPPORT
          LENCILQR(go->neg_lqr) +
#endif /* LQR_SUPPORT */
          LENCICBCP(go->neg_cbcp) + LENCILONG(go->neg_magicnumber) +
          LENCIVOID(go->neg_pcompression) + LENCIVOID(go->neg_accompression) +
#ifdef HAVE_MULTILINK
          LENCISHORT(go->neg_mrru) +
#endif /* HAVE_MULTILINK */
          LENCIVOID(go->neg_ssnhf) +
          (go->neg_endpoint ? CILEN_CHAR + go->endpoint.length : 0));
}

/*
 * lcp_addci - Add our desired CIs to a packet.
 */
static void lcp_addci(fsm *f, u_char *ucp, int *lenp) {
  ppp_pcb *pcb = f->pcb;
  lcp_options *go = &pcb->lcp_gotoptions;
  u_char *start_ucp = ucp;

#define ADDCIVOID(opt, neg)                                                    \
  if (neg) {                                                                   \
    PUTCHAR(opt, ucp);                                                         \
    PUTCHAR(CILEN_VOID, ucp);                                                  \
  }
#define ADDCISHORT(opt, neg, val)                                              \
  if (neg) {                                                                   \
    PUTCHAR(opt, ucp);                                                         \
    PUTCHAR(CILEN_SHORT, ucp);                                                 \
    PUTSHORT(val, ucp);                                                        \
  }
#if CHAP_SUPPORT
#define ADDCICHAP(opt, neg, val)                                               \
  if (neg) {                                                                   \
    PUTCHAR((opt), ucp);                                                       \
    PUTCHAR(CILEN_CHAP, ucp);                                                  \
    PUTSHORT(PPP_CHAP, ucp);                                                   \
    PUTCHAR((CHAP_DIGEST(val)), ucp);                                          \
  }
#endif /* CHAP_SUPPORT */
#define ADDCILONG(opt, neg, val)                                               \
  if (neg) {                                                                   \
    PUTCHAR(opt, ucp);                                                         \
    PUTCHAR(CILEN_LONG, ucp);                                                  \
    PUTLONG(val, ucp);                                                         \
  }
#if LQR_SUPPORT
#define ADDCILQR(opt, neg, val)                                                \
  if (neg) {                                                                   \
    PUTCHAR(opt, ucp);                                                         \
    PUTCHAR(CILEN_LQR, ucp);                                                   \
    PUTSHORT(PPP_LQR, ucp);                                                    \
    PUTLONG(val, ucp);                                                         \
  }
#endif /* LQR_SUPPORT */
#define ADDCICHAR(opt, neg, val)                                               \
  if (neg) {                                                                   \
    PUTCHAR(opt, ucp);                                                         \
    PUTCHAR(CILEN_CHAR, ucp);                                                  \
    PUTCHAR(val, ucp);                                                         \
  }
#define ADDCIENDP(opt, neg, class, val, len)                                   \
  if (neg) {                                                                   \
    int i;                                                                     \
    PUTCHAR(opt, ucp);                                                         \
    PUTCHAR(CILEN_CHAR + len, ucp);                                            \
    PUTCHAR(class, ucp);                                                       \
    for (i = 0; i < len; ++i)                                                  \
      PUTCHAR(val[i], ucp);                                                    \
  }

  ADDCISHORT(CI_MRU, go->neg_mru && go->mru != PPP_DEFMRU, go->mru);
  ADDCILONG(CI_ASYNCMAP, go->neg_asyncmap && go->asyncmap != 0xFFFFFFFF,
            go->asyncmap);
#if EAP_SUPPORT
  ADDCISHORT(CI_AUTHTYPE, go->neg_eap, PPP_EAP);
#endif           /* EAP_SUPPORT */
#if CHAP_SUPPORT /* cannot be improved, embedding a directive within macro     \
                    arguments is not portable */
#if EAP_SUPPORT
  ADDCICHAP(CI_AUTHTYPE, !go->neg_eap && go->neg_chap, go->chap_mdtype);
#endif /* EAP_SUPPORT */
#if !EAP_SUPPORT
  ADDCICHAP(CI_AUTHTYPE, go->neg_chap, go->chap_mdtype);
#endif          /* !EAP_SUPPORT */
#endif          /* CHAP_SUPPORT */
#if PAP_SUPPORT /* cannot be improved, embedding a directive within macro      \
                   arguments is not portable */
#if EAP_SUPPORT && CHAP_SUPPORT
  ADDCISHORT(CI_AUTHTYPE, !go->neg_eap && !go->neg_chap && go->neg_upap,
             PPP_PAP);
#endif /* EAP_SUPPORT && CHAP_SUPPORT */
#if EAP_SUPPORT && !CHAP_SUPPORT
  ADDCISHORT(CI_AUTHTYPE, !go->neg_eap && go->neg_upap, PPP_PAP);
#endif /* EAP_SUPPORT && !CHAP_SUPPORT */
#if !EAP_SUPPORT && CHAP_SUPPORT
  ADDCISHORT(CI_AUTHTYPE, !go->neg_chap && go->neg_upap, PPP_PAP);
#endif /* !EAP_SUPPORT && CHAP_SUPPORT */
#if !EAP_SUPPORT && !CHAP_SUPPORT
  ADDCISHORT(CI_AUTHTYPE, go->neg_upap, PPP_PAP);
#endif /* !EAP_SUPPORT && !CHAP_SUPPORT */
#endif /* PAP_SUPPORT */
#if LQR_SUPPORT
  ADDCILQR(CI_QUALITY, go->neg_lqr, go->lqr_period);
#endif /* LQR_SUPPORT */
  ADDCICHAR(CI_CALLBACK, go->neg_cbcp, CBCP_OPT);
  ADDCILONG(CI_MAGICNUMBER, go->neg_magicnumber, go->magicnumber);
  ADDCIVOID(CI_PCOMPRESSION, go->neg_pcompression);
  ADDCIVOID(CI_ACCOMPRESSION, go->neg_accompression);
#ifdef HAVE_MULTILINK
  ADDCISHORT(CI_MRRU, go->neg_mrru, go->mrru);
#endif
  ADDCIVOID(CI_SSNHF, go->neg_ssnhf);
  ADDCIENDP(CI_EPDISC, go->neg_endpoint, go->endpoint.class_,
            go->endpoint.value, go->endpoint.length);

  if (ucp - start_ucp != *lenp) {
    /* this should never happen, because peer_mtu should be 1500 */
    ppp_error(("Bug in lcp_addci: wrong length"));
  }
}

/*
 * lcp_ackci - Ack our CIs.
 * This should not modify any state if the Ack is bad.
 *
 * Returns:
 *	0 - Ack was bad.
 *	1 - Ack was good.
 */
static int lcp_ackci(fsm *f, u_char *p, int len) {
  ppp_pcb *pcb = f->pcb;
  lcp_options *go = &pcb->lcp_gotoptions;
  u_char cilen, citype, cichar;
  u_short cishort;
  u32_t cilong;

  /*
   * CIs must be in exactly the same order that we sent.
   * Check packet length and CI length at each step.
   * If we find any deviations, then this packet is bad.
   */
#define ACKCIVOID(opt, neg)                                                    \
  if (neg) {                                                                   \
    if ((len -= CILEN_VOID) < 0)                                               \
      goto bad;                                                                \
    GETCHAR(citype, p);                                                        \
    GETCHAR(cilen, p);                                                         \
    if (cilen != CILEN_VOID || citype != opt)                                  \
      goto bad;                                                                \
  }
#define ACKCISHORT(opt, neg, val)                                              \
  if (neg) {                                                                   \
    if ((len -= CILEN_SHORT) < 0)                                              \
      goto bad;                                                                \
    GETCHAR(citype, p);                                                        \
    GETCHAR(cilen, p);                                                         \
    if (cilen != CILEN_SHORT || citype != opt)                                 \
      goto bad;                                                                \
    GETSHORT(cishort, p);                                                      \
    if (cishort != val)                                                        \
      goto bad;                                                                \
  }
#define ACKCICHAR(opt, neg, val)                                               \
  if (neg) {                                                                   \
    if ((len -= CILEN_CHAR) < 0)                                               \
      goto bad;                                                                \
    GETCHAR(citype, p);                                                        \
    GETCHAR(cilen, p);                                                         \
    if (cilen != CILEN_CHAR || citype != opt)                                  \
      goto bad;                                                                \
    GETCHAR(cichar, p);                                                        \
    if (cichar != val)                                                         \
      goto bad;                                                                \
  }
#if CHAP_SUPPORT
#define ACKCICHAP(opt, neg, val)                                               \
  if (neg) {                                                                   \
    if ((len -= CILEN_CHAP) < 0)                                               \
      goto bad;                                                                \
    GETCHAR(citype, p);                                                        \
    GETCHAR(cilen, p);                                                         \
    if (cilen != CILEN_CHAP || citype != (opt))                                \
      goto bad;                                                                \
    GETSHORT(cishort, p);                                                      \
    if (cishort != PPP_CHAP)                                                   \
      goto bad;                                                                \
    GETCHAR(cichar, p);                                                        \
    if (cichar != (CHAP_DIGEST(val)))                                          \
      goto bad;                                                                \
  }
#endif /* CHAP_SUPPORT */
#define ACKCILONG(opt, neg, val)                                               \
  if (neg) {                                                                   \
    if ((len -= CILEN_LONG) < 0)                                               \
      goto bad;                                                                \
    GETCHAR(citype, p);                                                        \
    GETCHAR(cilen, p);                                                         \
    if (cilen != CILEN_LONG || citype != opt)                                  \
      goto bad;                                                                \
    GETLONG(cilong, p);                                                        \
    if (cilong != val)                                                         \
      goto bad;                                                                \
  }
#if LQR_SUPPORT
#define ACKCILQR(opt, neg, val)                                                \
  if (neg) {                                                                   \
    if ((len -= CILEN_LQR) < 0)                                                \
      goto bad;                                                                \
    GETCHAR(citype, p);                                                        \
    GETCHAR(cilen, p);                                                         \
    if (cilen != CILEN_LQR || citype != opt)                                   \
      goto bad;                                                                \
    GETSHORT(cishort, p);                                                      \
    if (cishort != PPP_LQR)                                                    \
      goto bad;                                                                \
    GETLONG(cilong, p);                                                        \
    if (cilong != val)                                                         \
      goto bad;                                                                \
  }
#endif /* LQR_SUPPORT */
#define ACKCIENDP(opt, neg, class, val, vlen)                                  \
  if (neg) {                                                                   \
    int i;                                                                     \
    if ((len -= CILEN_CHAR + vlen) < 0)                                        \
      goto bad;                                                                \
    GETCHAR(citype, p);                                                        \
    GETCHAR(cilen, p);                                                         \
    if (cilen != CILEN_CHAR + vlen || citype != opt)                           \
      goto bad;                                                                \
    GETCHAR(cichar, p);                                                        \
    if (cichar != class)                                                       \
      goto bad;                                                                \
    for (i = 0; i < vlen; ++i) {                                               \
      GETCHAR(cichar, p);                                                      \
      if (cichar != val[i])                                                    \
        goto bad;                                                              \
    }                                                                          \
  }

  ACKCISHORT(CI_MRU, go->neg_mru && go->mru != PPP_DEFMRU, go->mru);
  ACKCILONG(CI_ASYNCMAP, go->neg_asyncmap && go->asyncmap != 0xFFFFFFFF,
            go->asyncmap);
#if EAP_SUPPORT
  ACKCISHORT(CI_AUTHTYPE, go->neg_eap, PPP_EAP);
#endif           /* EAP_SUPPORT */
#if CHAP_SUPPORT /* cannot be improved, embedding a directive within macro     \
                    arguments is not portable */
#if EAP_SUPPORT
  ACKCICHAP(CI_AUTHTYPE, !go->neg_eap && go->neg_chap, go->chap_mdtype);
#endif /* EAP_SUPPORT */
#if !EAP_SUPPORT
  ACKCICHAP(CI_AUTHTYPE, go->neg_chap, go->chap_mdtype);
#endif          /* !EAP_SUPPORT */
#endif          /* CHAP_SUPPORT */
#if PAP_SUPPORT /* cannot be improved, embedding a directive within macro      \
                   arguments is not portable */
#if EAP_SUPPORT && CHAP_SUPPORT
  ACKCISHORT(CI_AUTHTYPE, !go->neg_eap && !go->neg_chap && go->neg_upap,
             PPP_PAP);
#endif /* EAP_SUPPORT && CHAP_SUPPORT */
#if EAP_SUPPORT && !CHAP_SUPPORT
  ACKCISHORT(CI_AUTHTYPE, !go->neg_eap && go->neg_upap, PPP_PAP);
#endif /* EAP_SUPPORT && !CHAP_SUPPORT */
#if !EAP_SUPPORT && CHAP_SUPPORT
  ACKCISHORT(CI_AUTHTYPE, !go->neg_chap && go->neg_upap, PPP_PAP);
#endif /* !EAP_SUPPORT && CHAP_SUPPORT */
#if !EAP_SUPPORT && !CHAP_SUPPORT
  ACKCISHORT(CI_AUTHTYPE, go->neg_upap, PPP_PAP);
#endif /* !EAP_SUPPORT && !CHAP_SUPPORT */
#endif /* PAP_SUPPORT */
#if LQR_SUPPORT
  ACKCILQR(CI_QUALITY, go->neg_lqr, go->lqr_period);
#endif /* LQR_SUPPORT */
  ACKCICHAR(CI_CALLBACK, go->neg_cbcp, CBCP_OPT);
  ACKCILONG(CI_MAGICNUMBER, go->neg_magicnumber, go->magicnumber);
  ACKCIVOID(CI_PCOMPRESSION, go->neg_pcompression);
  ACKCIVOID(CI_ACCOMPRESSION, go->neg_accompression);
#ifdef HAVE_MULTILINK
  ACKCISHORT(CI_MRRU, go->neg_mrru, go->mrru);
#endif /* HAVE_MULTILINK */
  ACKCIVOID(CI_SSNHF, go->neg_ssnhf);
  ACKCIENDP(CI_EPDISC, go->neg_endpoint, go->endpoint.class_,
            go->endpoint.value, go->endpoint.length);

  /*
   * If there are any remaining CIs, then this packet is bad.
   */
  if (len != 0)
    goto bad;
  return (1);
bad:
  LCPDEBUG(("lcp_acki: received bad Ack!"));
  return (0);
}

/*
 * lcp_nakci - Peer has sent a NAK for some of our CIs.
 * This should not modify any state if the Nak is bad
 * or if LCP is in the OPENED state.
 *
 * Returns:
 *	0 - Nak was bad.
 *	1 - Nak was good.
 */
static int lcp_nakci(fsm *f, u_char *p, int len, int treat_as_reject) {
  ppp_pcb *pcb = f->pcb;
  lcp_options *go = &pcb->lcp_gotoptions;
  lcp_options *wo = &pcb->lcp_wantoptions;
  u_char citype, cichar, *next;
  u_short cishort;
  u32_t cilong;
  lcp_options no;   /* options we've seen Naks for */
  lcp_options try_; /* options to request next time */
  int looped_back = 0;
  int cilen;

  BZERO(&no, sizeof(no));
  try_ = *go;

  /*
   * Any Nak'd CIs must be in exactly the same order that we sent.
   * Check packet length and CI length at each step.
   * If we find any deviations, then this packet is bad.
   */
#define NAKCIVOID(opt, neg)                                                    \
  if (go->neg && len >= CILEN_VOID && p[1] == CILEN_VOID && p[0] == opt) {     \
    len -= CILEN_VOID;                                                         \
    INCPTR(CILEN_VOID, p);                                                     \
    no.neg = 1;                                                                \
    try_.neg = 0;                                                              \
  }
#if CHAP_SUPPORT
#define NAKCICHAP(opt, neg, code)                                              \
  if (go->neg && len >= CILEN_CHAP && p[1] == CILEN_CHAP && p[0] == opt) {     \
    len -= CILEN_CHAP;                                                         \
    INCPTR(2, p);                                                              \
    GETSHORT(cishort, p);                                                      \
    GETCHAR(cichar, p);                                                        \
    no.neg = 1;                                                                \
    code                                                                       \
  }
#endif /* CHAP_SUPPORT */
#define NAKCICHAR(opt, neg, code)                                              \
  if (go->neg && len >= CILEN_CHAR && p[1] == CILEN_CHAR && p[0] == opt) {     \
    len -= CILEN_CHAR;                                                         \
    INCPTR(2, p);                                                              \
    GETCHAR(cichar, p);                                                        \
    no.neg = 1;                                                                \
    code                                                                       \
  }
#define NAKCISHORT(opt, neg, code)                                             \
  if (go->neg && len >= CILEN_SHORT && p[1] == CILEN_SHORT && p[0] == opt) {   \
    len -= CILEN_SHORT;                                                        \
    INCPTR(2, p);                                                              \
    GETSHORT(cishort, p);                                                      \
    no.neg = 1;                                                                \
    code                                                                       \
  }
#define NAKCILONG(opt, neg, code)                                              \
  if (go->neg && len >= CILEN_LONG && p[1] == CILEN_LONG && p[0] == opt) {     \
    len -= CILEN_LONG;                                                         \
    INCPTR(2, p);                                                              \
    GETLONG(cilong, p);                                                        \
    no.neg = 1;                                                                \
    code                                                                       \
  }
#if LQR_SUPPORT
#define NAKCILQR(opt, neg, code)                                               \
  if (go->neg && len >= CILEN_LQR && p[1] == CILEN_LQR && p[0] == opt) {       \
    len -= CILEN_LQR;                                                          \
    INCPTR(2, p);                                                              \
    GETSHORT(cishort, p);                                                      \
    GETLONG(cilong, p);                                                        \
    no.neg = 1;                                                                \
    code                                                                       \
  }
#endif /* LQR_SUPPORT */
#define NAKCIENDP(opt, neg)                                                    \
  if (go->neg && len >= CILEN_CHAR && p[0] == opt && p[1] >= CILEN_CHAR &&     \
      p[1] <= len) {                                                           \
    len -= p[1];                                                               \
    INCPTR(p[1], p);                                                           \
    no.neg = 1;                                                                \
    try_.neg = 0;                                                              \
  }

  /*
   * NOTE!  There must be no assignments to individual fields of *go in
   * the code below.  Any such assignment is a BUG!
   */
  /*
   * We don't care if they want to send us smaller packets than
   * we want.  Therefore, accept any MRU less than what we asked for,
   * but then ignore the new value when setting the MRU in the kernel.
   * If they send us a bigger MRU than what we asked, accept it, up to
   * the limit of the default MRU we'd get if we didn't negotiate.
   */
  if (go->neg_mru && go->mru != PPP_DEFMRU) {
    NAKCISHORT(CI_MRU, neg_mru,
               if (cishort <= wo->mru || cishort <= PPP_DEFMRU) try_.mru =
                   cishort;);
  }

  /*
   * Add any characters they want to our (receive-side) asyncmap.
   */
  if (go->neg_asyncmap && go->asyncmap != 0xFFFFFFFF) {
    NAKCILONG(CI_ASYNCMAP, neg_asyncmap,
              try_.asyncmap = go->asyncmap | cilong;);
  }

  /*
   * If they've nak'd our authentication-protocol, check whether
   * they are proposing a different protocol, or a different
   * hash algorithm for CHAP.
   */
  if ((0
#if CHAP_SUPPORT
       || go->neg_chap
#endif /* CHAP_SUPPORT */
#if PAP_SUPPORT
       || go->neg_upap
#endif /* PAP_SUPPORT */
#if EAP_SUPPORT
       || go->neg_eap
#endif /* EAP_SUPPORT */
       ) &&
      len >= CILEN_SHORT && p[0] == CI_AUTHTYPE && p[1] >= CILEN_SHORT &&
      p[1] <= len) {
    cilen = p[1];
    len -= cilen;
#if CHAP_SUPPORT
    no.neg_chap = go->neg_chap;
#endif /* CHAP_SUPPORT */
#if PAP_SUPPORT
    no.neg_upap = go->neg_upap;
#endif /* PAP_SUPPORT */
#if EAP_SUPPORT
    no.neg_eap = go->neg_eap;
#endif /* EAP_SUPPORT */
    INCPTR(2, p);
    GETSHORT(cishort, p);

#if PAP_SUPPORT
    if (cishort == PPP_PAP && cilen == CILEN_SHORT) {
#if EAP_SUPPORT
      /* If we were asking for EAP, then we need to stop that. */
      if (go->neg_eap)
        try_.neg_eap = 0;
      else
#endif /* EAP_SUPPORT */

#if CHAP_SUPPORT
        /* If we were asking for CHAP, then we need to stop that. */
        if (go->neg_chap)
          try_.neg_chap = 0;
        else
#endif /* CHAP_SUPPORT */

          /*
           * If we weren't asking for CHAP or EAP, then we were asking for
           * PAP, in which case this Nak is bad.
           */
          goto bad;
    } else
#endif /* PAP_SUPPORT */

#if CHAP_SUPPORT
        if (cishort == PPP_CHAP && cilen == CILEN_CHAP) {
      GETCHAR(cichar, p);
#if EAP_SUPPORT
      /* Stop asking for EAP, if we were. */
      if (go->neg_eap) {
        try_.neg_eap = 0;
        /* Try to set up to use their suggestion, if possible */
        if (CHAP_CANDIGEST(go->chap_mdtype, cichar))
          try_.chap_mdtype = CHAP_MDTYPE_D(cichar);
      } else
#endif /* EAP_SUPPORT */
        if (go->neg_chap) {
          /*
           * We were asking for our preferred algorithm, they must
           * want something different.
           */
          if (cichar != CHAP_DIGEST(go->chap_mdtype)) {
            if (CHAP_CANDIGEST(go->chap_mdtype, cichar)) {
              /* Use their suggestion if we support it ... */
              try_.chap_mdtype = CHAP_MDTYPE_D(cichar);
            } else {
              /* ... otherwise, try our next-preferred algorithm. */
              try_.chap_mdtype &= ~(CHAP_MDTYPE(try_.chap_mdtype));
              if (try_.chap_mdtype == MDTYPE_NONE) /* out of algos */
                try_.neg_chap = 0;
            }
          } else {
            /*
             * Whoops, they Nak'd our algorithm of choice
             * but then suggested it back to us.
             */
            goto bad;
          }
        } else {
          /*
           * Stop asking for PAP if we were asking for it.
           */
#if PAP_SUPPORT
          try_.neg_upap = 0;
#endif /* PAP_SUPPORT */
        }

    } else
#endif /* CHAP_SUPPORT */
    {

#if EAP_SUPPORT
      /*
       * If we were asking for EAP, and they're Conf-Naking EAP,
       * well, that's just strange.  Nobody should do that.
       */
      if (cishort == PPP_EAP && cilen == CILEN_SHORT && go->neg_eap)
        ppp_dbglog(("Unexpected Conf-Nak for EAP"));

      /*
       * We don't recognize what they're suggesting.
       * Stop asking for what we were asking for.
       */
      if (go->neg_eap)
        try_.neg_eap = 0;
      else
#endif /* EAP_SUPPORT */

#if CHAP_SUPPORT
          if (go->neg_chap)
        try_.neg_chap = 0;
      else
#endif /* CHAP_SUPPORT */

#if PAP_SUPPORT
          if (1)
        try_.neg_upap = 0;
      else
#endif /* PAP_SUPPORT */
      {
      }

      p += cilen - CILEN_SHORT;
    }
  }

#if LQR_SUPPORT
  /*
   * If they can't cope with our link quality protocol, we'll have
   * to stop asking for LQR.  We haven't got any other protocol.
   * If they Nak the reporting period, take their value XXX ?
   */
  NAKCILQR(CI_QUALITY, neg_lqr, if (cishort != PPP_LQR) try_.neg_lqr = 0;
           else try_.lqr_period = cilong;);
#endif /* LQR_SUPPORT */

  /*
   * Only implementing CBCP...not the rest of the callback options
   */
  NAKCICHAR(CI_CALLBACK, neg_cbcp, try_.neg_cbcp = 0;
            (void)cichar; /* if CHAP support is not compiled, cichar is set but
                             not used, which makes some compilers complaining */
  );

  /*
   * Check for a looped-back line.
   */
  NAKCILONG(CI_MAGICNUMBER, neg_magicnumber, try_.magicnumber = magic();
            looped_back = 1;);

  /*
   * Peer shouldn't send Nak for protocol compression or
   * address/control compression requests; they should send
   * a Reject instead.  If they send a Nak, treat it as a Reject.
   */
  NAKCIVOID(CI_PCOMPRESSION, neg_pcompression);
  NAKCIVOID(CI_ACCOMPRESSION, neg_accompression);

#ifdef HAVE_MULTILINK
  /*
   * Nak for MRRU option - accept their value if it is smaller
   * than the one we want.
   */
  if (go->neg_mrru) {
    NAKCISHORT(CI_MRRU, neg_mrru, if (treat_as_reject) try_.neg_mrru = 0;
               else if (cishort <= wo->mrru) try_.mrru = cishort;);
  }
#else  /* HAVE_MULTILINK */
  LWIP_UNUSED_ARG(treat_as_reject);
#endif /* HAVE_MULTILINK */

  /*
   * Nak for short sequence numbers shouldn't be sent, treat it
   * like a reject.
   */
  NAKCIVOID(CI_SSNHF, neg_ssnhf);

  /*
   * Nak of the endpoint discriminator option is not permitted,
   * treat it like a reject.
   */
  NAKCIENDP(CI_EPDISC, neg_endpoint);

  /*
   * There may be remaining CIs, if the peer is requesting negotiation
   * on an option that we didn't include in our request packet.
   * If we see an option that we requested, or one we've already seen
   * in this packet, then this packet is bad.
   * If we wanted to respond by starting to negotiate on the requested
   * option(s), we could, but we don't, because except for the
   * authentication type and quality protocol, if we are not negotiating
   * an option, it is because we were told not to.
   * For the authentication type, the Nak from the peer means
   * `let me authenticate myself with you' which is a bit pointless.
   * For the quality protocol, the Nak means `ask me to send you quality
   * reports', but if we didn't ask for them, we don't want them.
   * An option we don't recognize represents the peer asking to
   * negotiate some option we don't support, so ignore it.
   */
  while (len >= CILEN_VOID) {
    GETCHAR(citype, p);
    GETCHAR(cilen, p);
    if (cilen < CILEN_VOID || (len -= cilen) < 0)
      goto bad;
    next = p + cilen - 2;

    switch (citype) {
    case CI_MRU:
      if ((go->neg_mru && go->mru != PPP_DEFMRU) || no.neg_mru ||
          cilen != CILEN_SHORT)
        goto bad;
      GETSHORT(cishort, p);
      if (cishort < PPP_DEFMRU) {
        try_.neg_mru = 1;
        try_.mru = cishort;
      }
      break;
    case CI_ASYNCMAP:
      if ((go->neg_asyncmap && go->asyncmap != 0xFFFFFFFF) || no.neg_asyncmap ||
          cilen != CILEN_LONG)
        goto bad;
      break;
    case CI_AUTHTYPE:
      /* This is potentially dead code (#if !PPP_AUTH_SUPPORT)
       * Thus the double parantheses to mark the code explicitely
       * disabled when building with clang
       */
      if ((0
#if CHAP_SUPPORT
           || go->neg_chap || no.neg_chap
#endif /* CHAP_SUPPORT */
#if PAP_SUPPORT
           || go->neg_upap || no.neg_upap
#endif /* PAP_SUPPORT */
#if EAP_SUPPORT
           || go->neg_eap || no.neg_eap
#endif /* EAP_SUPPORT */
           ))
        goto bad;
      break;
    case CI_MAGICNUMBER:
      if (go->neg_magicnumber || no.neg_magicnumber || cilen != CILEN_LONG)
        goto bad;
      break;
    case CI_PCOMPRESSION:
      if (go->neg_pcompression || no.neg_pcompression || cilen != CILEN_VOID)
        goto bad;
      break;
    case CI_ACCOMPRESSION:
      if (go->neg_accompression || no.neg_accompression || cilen != CILEN_VOID)
        goto bad;
      break;
#if LQR_SUPPORT
    case CI_QUALITY:
      if (go->neg_lqr || no.neg_lqr || cilen != CILEN_LQR)
        goto bad;
      break;
#endif /* LQR_SUPPORT */
#ifdef HAVE_MULTILINK
    case CI_MRRU:
      if (go->neg_mrru || no.neg_mrru || cilen != CILEN_SHORT)
        goto bad;
      break;
#endif /* HAVE_MULTILINK */
    case CI_SSNHF:
      if (go->neg_ssnhf || no.neg_ssnhf || cilen != CILEN_VOID)
        goto bad;
      try_.neg_ssnhf = 1;
      break;
    case CI_EPDISC:
      if (go->neg_endpoint || no.neg_endpoint || cilen < CILEN_CHAR)
        goto bad;
      break;
    default:
      break;
    }
    p = next;
  }

  /*
   * OK, the Nak is good.  Now we can update state.
   * If there are any options left we ignore them.
   */
  if (f->state != PPP_FSM_OPENED) {
    if (looped_back) {
      if (++try_.numloops >= pcb->settings.lcp_loopbackfail) {
        ppp_notice(("Serial line is looped back."));
        pcb->err_code = PPPERR_LOOPBACK;
        lcp_close(f->pcb, "Loopback detected");
      }
    } else
      try_.numloops = 0;
    *go = try_;
  }

  return 1;

bad:
  LCPDEBUG(("lcp_nakci: received bad Nak!"));
  return 0;
}

/*
 * lcp_rejci - Peer has Rejected some of our CIs.
 * This should not modify any state if the Reject is bad
 * or if LCP is in the OPENED state.
 *
 * Returns:
 *	0 - Reject was bad.
 *	1 - Reject was good.
 */
static int lcp_rejci(fsm *f, u_char *p, int len) {
  ppp_pcb *pcb = f->pcb;
  lcp_options *go = &pcb->lcp_gotoptions;
  u_char cichar;
  u_short cishort;
  u32_t cilong;
  lcp_options try_; /* options to request next time */

  try_ = *go;

  /*
   * Any Rejected CIs must be in exactly the same order that we sent.
   * Check packet length and CI length at each step.
   * If we find any deviations, then this packet is bad.
   */
#define REJCIVOID(opt, neg)                                                    \
  if (go->neg && len >= CILEN_VOID && p[1] == CILEN_VOID && p[0] == opt) {     \
    len -= CILEN_VOID;                                                         \
    INCPTR(CILEN_VOID, p);                                                     \
    try_.neg = 0;                                                              \
  }
#define REJCISHORT(opt, neg, val)                                              \
  if (go->neg && len >= CILEN_SHORT && p[1] == CILEN_SHORT && p[0] == opt) {   \
    len -= CILEN_SHORT;                                                        \
    INCPTR(2, p);                                                              \
    GETSHORT(cishort, p);                                                      \
    /* Check rejected value. */                                                \
    if (cishort != val)                                                        \
      goto bad;                                                                \
    try_.neg = 0;                                                              \
  }

#if CHAP_SUPPORT && EAP_SUPPORT && PAP_SUPPORT
#define REJCICHAP(opt, neg, val)                                               \
  if (go->neg && len >= CILEN_CHAP && p[1] == CILEN_CHAP && p[0] == opt) {     \
    len -= CILEN_CHAP;                                                         \
    INCPTR(2, p);                                                              \
    GETSHORT(cishort, p);                                                      \
    GETCHAR(cichar, p);                                                        \
    /* Check rejected value. */                                                \
    if ((cishort != PPP_CHAP) || (cichar != (CHAP_DIGEST(val))))               \
      goto bad;                                                                \
    try_.neg = 0;                                                              \
    try_.neg_eap = try_.neg_upap = 0;                                          \
  }
#endif /* CHAP_SUPPORT && EAP_SUPPORT && PAP_SUPPORT */

#if CHAP_SUPPORT && !EAP_SUPPORT && PAP_SUPPORT
#define REJCICHAP(opt, neg, val)                                               \
  if (go->neg && len >= CILEN_CHAP && p[1] == CILEN_CHAP && p[0] == opt) {     \
    len -= CILEN_CHAP;                                                         \
    INCPTR(2, p);                                                              \
    GETSHORT(cishort, p);                                                      \
    GETCHAR(cichar, p);                                                        \
    /* Check rejected value. */                                                \
    if ((cishort != PPP_CHAP) || (cichar != (CHAP_DIGEST(val))))               \
      goto bad;                                                                \
    try_.neg = 0;                                                              \
    try_.neg_upap = 0;                                                         \
  }
#endif /* CHAP_SUPPORT && !EAP_SUPPORT && PAP_SUPPORT */

#if CHAP_SUPPORT && EAP_SUPPORT && !PAP_SUPPORT
#define REJCICHAP(opt, neg, val)                                               \
  if (go->neg && len >= CILEN_CHAP && p[1] == CILEN_CHAP && p[0] == opt) {     \
    len -= CILEN_CHAP;                                                         \
    INCPTR(2, p);                                                              \
    GETSHORT(cishort, p);                                                      \
    GETCHAR(cichar, p);                                                        \
    /* Check rejected value. */                                                \
    if ((cishort != PPP_CHAP) || (cichar != (CHAP_DIGEST(val))))               \
      goto bad;                                                                \
    try_.neg = 0;                                                              \
    try_.neg_eap = 0;                                                          \
  }
#endif /* CHAP_SUPPORT && EAP_SUPPORT && !PAP_SUPPORT */

#if CHAP_SUPPORT && !EAP_SUPPORT && !PAP_SUPPORT
#define REJCICHAP(opt, neg, val)                                               \
  if (go->neg && len >= CILEN_CHAP && p[1] == CILEN_CHAP && p[0] == opt) {     \
    len -= CILEN_CHAP;                                                         \
    INCPTR(2, p);                                                              \
    GETSHORT(cishort, p);                                                      \
    GETCHAR(cichar, p);                                                        \
    /* Check rejected value. */                                                \
    if ((cishort != PPP_CHAP) || (cichar != (CHAP_DIGEST(val))))               \
      goto bad;                                                                \
    try_.neg = 0;                                                              \
  }
#endif /* CHAP_SUPPORT && !EAP_SUPPORT && !PAP_SUPPORT */

#define REJCILONG(opt, neg, val)                                               \
  if (go->neg && len >= CILEN_LONG && p[1] == CILEN_LONG && p[0] == opt) {     \
    len -= CILEN_LONG;                                                         \
    INCPTR(2, p);                                                              \
    GETLONG(cilong, p);                                                        \
    /* Check rejected value. */                                                \
    if (cilong != val)                                                         \
      goto bad;                                                                \
    try_.neg = 0;                                                              \
  }
#if LQR_SUPPORT
#define REJCILQR(opt, neg, val)                                                \
  if (go->neg && len >= CILEN_LQR && p[1] == CILEN_LQR && p[0] == opt) {       \
    len -= CILEN_LQR;                                                          \
    INCPTR(2, p);                                                              \
    GETSHORT(cishort, p);                                                      \
    GETLONG(cilong, p);                                                        \
    /* Check rejected value. */                                                \
    if (cishort != PPP_LQR || cilong != val)                                   \
      goto bad;                                                                \
    try_.neg = 0;                                                              \
  }
#endif /* LQR_SUPPORT */
#define REJCICBCP(opt, neg, val)                                               \
  if (go->neg && len >= CILEN_CBCP && p[1] == CILEN_CBCP && p[0] == opt) {     \
    len -= CILEN_CBCP;                                                         \
    INCPTR(2, p);                                                              \
    GETCHAR(cichar, p);                                                        \
    /* Check rejected value. */                                                \
    if (cichar != val)                                                         \
      goto bad;                                                                \
    try_.neg = 0;                                                              \
  }
#define REJCIENDP(opt, neg, class, val, vlen)                                  \
  if (go->neg && len >= CILEN_CHAR + vlen && p[0] == opt &&                    \
      p[1] == CILEN_CHAR + vlen) {                                             \
    int i;                                                                     \
    len -= CILEN_CHAR + vlen;                                                  \
    INCPTR(2, p);                                                              \
    GETCHAR(cichar, p);                                                        \
    if (cichar != class)                                                       \
      goto bad;                                                                \
    for (i = 0; i < vlen; ++i) {                                               \
      GETCHAR(cichar, p);                                                      \
      if (cichar != val[i])                                                    \
        goto bad;                                                              \
    }                                                                          \
    try_.neg = 0;                                                              \
  }

  REJCISHORT(CI_MRU, neg_mru, go->mru);
  REJCILONG(CI_ASYNCMAP, neg_asyncmap, go->asyncmap);
#if EAP_SUPPORT
  REJCISHORT(CI_AUTHTYPE, neg_eap, PPP_EAP);
  if (!go->neg_eap) {
#endif /* EAP_SUPPORT */
#if CHAP_SUPPORT
    REJCICHAP(CI_AUTHTYPE, neg_chap, go->chap_mdtype);
    if (!go->neg_chap) {
#endif /* CHAP_SUPPORT */
#if PAP_SUPPORT
      REJCISHORT(CI_AUTHTYPE, neg_upap, PPP_PAP);
#endif /* PAP_SUPPORT */
#if CHAP_SUPPORT
    }
#endif /* CHAP_SUPPORT */
#if EAP_SUPPORT
  }
#endif /* EAP_SUPPORT */
#if LQR_SUPPORT
  REJCILQR(CI_QUALITY, neg_lqr, go->lqr_period);
#endif /* LQR_SUPPORT */
  REJCICBCP(CI_CALLBACK, neg_cbcp, CBCP_OPT);
  REJCILONG(CI_MAGICNUMBER, neg_magicnumber, go->magicnumber);
  REJCIVOID(CI_PCOMPRESSION, neg_pcompression);
  REJCIVOID(CI_ACCOMPRESSION, neg_accompression);
#ifdef HAVE_MULTILINK
  REJCISHORT(CI_MRRU, neg_mrru, go->mrru);
#endif /* HAVE_MULTILINK */
  REJCIVOID(CI_SSNHF, neg_ssnhf);
  REJCIENDP(CI_EPDISC, neg_endpoint, go->endpoint.class_, go->endpoint.value,
            go->endpoint.length);

  /*
   * If there are any remaining CIs, then this packet is bad.
   */
  if (len != 0)
    goto bad;
  /*
   * Now we can update state.
   */
  if (f->state != PPP_FSM_OPENED)
    *go = try_;
  return 1;

bad:
  LCPDEBUG(("lcp_rejci: received bad Reject!"));
  return 0;
}

/*
 * lcp_reqci - Check the peer's requested CIs and send appropriate response.
 *
 * Returns: CONFACK, CONFNAK or CONFREJ and input packet modified
 * appropriately.  If reject_if_disagree is non-zero, doesn't return
 * CONFNAK; returns CONFREJ if it can't return CONFACK.
 *
 * inp = Requested CIs
 * lenp = Length of requested CIs
 */
static int lcp_reqci(fsm *f, u_char *inp, int *lenp, int reject_if_disagree) {
  ppp_pcb *pcb = f->pcb;
  lcp_options *go = &pcb->lcp_gotoptions;
  lcp_options *ho = &pcb->lcp_hisoptions;
  lcp_options *ao = &pcb->lcp_allowoptions;
  u_char *cip, *next;        /* Pointer to current and next CIs */
  int cilen, citype, cichar; /* Parsed len, type, char value */
  u_short cishort;           /* Parsed short value */
  u32_t cilong;              /* Parse long value */
  int rc = CONFACK;          /* Final packet return code */
  int orc;                   /* Individual option return code */
  u_char *p;                 /* Pointer to next char to parse */
  u_char *rejp;              /* Pointer to next char in reject frame */
  struct pbuf *nakp;         /* Nak buffer */
  u_char *nakoutp;           /* Pointer to next char in Nak frame */
  int l = *lenp;             /* Length left */

  /*
   * Reset all his options.
   */
  BZERO(ho, sizeof(*ho));

  /*
   * Process all his options.
   */
  next = inp;
  nakp = pbuf_alloc(PBUF_RAW, (u16_t)(PPP_CTRL_PBUF_UNKNOWN_SIZE), PBUF_RAM);
  if (NULL == nakp)
    return 0;
  if (nakp->tot_len != nakp->len) {
    pbuf_free(nakp);
    return 0;
  }

  nakoutp = (u_char *)nakp->payload;
  rejp = inp;
  while (l) {
    orc = CONFACK;  /* Assume success */
    cip = p = next; /* Remember beginning of CI */
    if (l < 2 ||    /* Not enough data for CI header or */
        p[1] < 2 || /*  CI length too small or */
        p[1] > l) { /*  CI length too big? */
      LCPDEBUG(("lcp_reqci: bad CI length!"));
      orc = CONFREJ; /* Reject bad CI */
      cilen = l;     /* Reject till end of packet */
      l = 0;         /* Don't loop again */
      citype = 0;
      goto endswitch;
    }
    GETCHAR(citype, p); /* Parse CI type */
    GETCHAR(cilen, p);  /* Parse CI length */
    l -= cilen;         /* Adjust remaining length */
    next += cilen;      /* Step to next CI */

    switch (citype) { /* Check CI type */
    case CI_MRU:
      if (!ao->neg_mru ||         /* Allow option? */
          cilen != CILEN_SHORT) { /* Check CI length */
        orc = CONFREJ;            /* Reject CI */
        break;
      }
      GETSHORT(cishort, p); /* Parse MRU */

      /*
       * He must be able to receive at least our minimum.
       * No need to check a maximum.  If he sends a large number,
       * we'll just ignore it.
       */
      if (cishort < PPP_MINMRU) {
        orc = CONFNAK; /* Nak CI */
        PUTCHAR(CI_MRU, nakoutp);
        PUTCHAR(CILEN_SHORT, nakoutp);
        PUTSHORT(PPP_MINMRU, nakoutp); /* Give him a hint */
        break;
      }
      ho->neg_mru = 1;   /* Remember he sent MRU */
      ho->mru = cishort; /* And remember value */
      break;

    case CI_ASYNCMAP:
      if (!ao->neg_asyncmap || cilen != CILEN_LONG) {
        orc = CONFREJ;
        break;
      }
      GETLONG(cilong, p);

      /*
       * Asyncmap must have set at least the bits
       * which are set in lcp_allowoptions[unit].asyncmap.
       */
      if ((ao->asyncmap & ~cilong) != 0) {
        orc = CONFNAK;
        PUTCHAR(CI_ASYNCMAP, nakoutp);
        PUTCHAR(CILEN_LONG, nakoutp);
        PUTLONG(ao->asyncmap | cilong, nakoutp);
        break;
      }
      ho->neg_asyncmap = 1;
      ho->asyncmap = cilong;
      break;

    case CI_AUTHTYPE:
      if (cilen < CILEN_SHORT || !(0
#if PAP_SUPPORT
                                   || ao->neg_upap
#endif /* PAP_SUPPORT */
#if CHAP_SUPPORT
                                   || ao->neg_chap
#endif /* CHAP_SUPPORT */
#if EAP_SUPPORT
                                   || ao->neg_eap
#endif /* EAP_SUPPORT */
                                   )) {
        /*
         * Reject the option if we're not willing to authenticate.
         */
        ppp_dbglog(("No auth is possible"));
        orc = CONFREJ;
        break;
      }
      GETSHORT(cishort, p);

      /*
       * Authtype must be PAP, CHAP, or EAP.
       *
       * Note: if more than one of ao->neg_upap, ao->neg_chap, and
       * ao->neg_eap are set, and the peer sends a Configure-Request
       * with two or more authenticate-protocol requests, then we will
       * reject the second request.
       * Whether we end up doing CHAP, UPAP, or EAP depends then on
       * the ordering of the CIs in the peer's Configure-Request.
       */

#if PAP_SUPPORT
      if (cishort == PPP_PAP) {
        /* we've already accepted CHAP or EAP */
        if (0
#if CHAP_SUPPORT
            || ho->neg_chap
#endif /* CHAP_SUPPORT */
#if EAP_SUPPORT
            || ho->neg_eap
#endif /* EAP_SUPPORT */
            || cilen != CILEN_SHORT) {
          LCPDEBUG(("lcp_reqci: rcvd AUTHTYPE PAP, rejecting..."));
          orc = CONFREJ;
          break;
        }
        if (!ao->neg_upap) { /* we don't want to do PAP */
          orc = CONFNAK;     /* NAK it and suggest CHAP or EAP */
          PUTCHAR(CI_AUTHTYPE, nakoutp);
#if EAP_SUPPORT
          if (ao->neg_eap) {
            PUTCHAR(CILEN_SHORT, nakoutp);
            PUTSHORT(PPP_EAP, nakoutp);
          } else {
#endif /* EAP_SUPPORT */
#if CHAP_SUPPORT
            PUTCHAR(CILEN_CHAP, nakoutp);
            PUTSHORT(PPP_CHAP, nakoutp);
            PUTCHAR(CHAP_DIGEST(ao->chap_mdtype), nakoutp);
#endif /* CHAP_SUPPORT */
#if EAP_SUPPORT
          }
#endif /* EAP_SUPPORT */
          break;
        }
        ho->neg_upap = 1;
        break;
      }
#endif /* PAP_SUPPORT */
#if CHAP_SUPPORT
      if (cishort == PPP_CHAP) {
        /* we've already accepted PAP or EAP */
        if (
#if PAP_SUPPORT
            ho->neg_upap ||
#endif /* PAP_SUPPORT */
#if EAP_SUPPORT
            ho->neg_eap ||
#endif /* EAP_SUPPORT */
            cilen != CILEN_CHAP) {
          LCPDEBUG(("lcp_reqci: rcvd AUTHTYPE CHAP, rejecting..."));
          orc = CONFREJ;
          break;
        }
        if (!ao->neg_chap) { /* we don't want to do CHAP */
          orc = CONFNAK;     /* NAK it and suggest EAP or PAP */
          PUTCHAR(CI_AUTHTYPE, nakoutp);
          PUTCHAR(CILEN_SHORT, nakoutp);
#if EAP_SUPPORT
          if (ao->neg_eap) {
            PUTSHORT(PPP_EAP, nakoutp);
          } else
#endif /* EAP_SUPPORT */
#if PAP_SUPPORT
              if (1) {
            PUTSHORT(PPP_PAP, nakoutp);
          } else
#endif /* PAP_SUPPORT */
          {
          }
          break;
        }
        GETCHAR(cichar, p); /* get digest type */
        if (!(CHAP_CANDIGEST(ao->chap_mdtype, cichar))) {
          /*
           * We can't/won't do the requested type,
           * suggest something else.
           */
          orc = CONFNAK;
          PUTCHAR(CI_AUTHTYPE, nakoutp);
          PUTCHAR(CILEN_CHAP, nakoutp);
          PUTSHORT(PPP_CHAP, nakoutp);
          PUTCHAR(CHAP_DIGEST(ao->chap_mdtype), nakoutp);
          break;
        }
        ho->chap_mdtype = CHAP_MDTYPE_D(cichar); /* save md type */
        ho->neg_chap = 1;
        break;
      }
#endif /* CHAP_SUPPORT */
#if EAP_SUPPORT
      if (cishort == PPP_EAP) {
        /* we've already accepted CHAP or PAP */
        if (
#if CHAP_SUPPORT
            ho->neg_chap ||
#endif /* CHAP_SUPPORT */
#if PAP_SUPPORT
            ho->neg_upap ||
#endif /* PAP_SUPPORT */
            cilen != CILEN_SHORT) {
          LCPDEBUG(("lcp_reqci: rcvd AUTHTYPE EAP, rejecting..."));
          orc = CONFREJ;
          break;
        }
        if (!ao->neg_eap) { /* we don't want to do EAP */
          orc = CONFNAK;    /* NAK it and suggest CHAP or PAP */
          PUTCHAR(CI_AUTHTYPE, nakoutp);
#if CHAP_SUPPORT
          if (ao->neg_chap) {
            PUTCHAR(CILEN_CHAP, nakoutp);
            PUTSHORT(PPP_CHAP, nakoutp);
            PUTCHAR(CHAP_DIGEST(ao->chap_mdtype), nakoutp);
          } else
#endif /* CHAP_SUPPORT */
#if PAP_SUPPORT
              if (1) {
            PUTCHAR(CILEN_SHORT, nakoutp);
            PUTSHORT(PPP_PAP, nakoutp);
          } else
#endif /* PAP_SUPPORT */
          {
          }
          break;
        }
        ho->neg_eap = 1;
        break;
      }
#endif /* EAP_SUPPORT */

      /*
       * We don't recognize the protocol they're asking for.
       * Nak it with something we're willing to do.
       * (At this point we know ao->neg_upap || ao->neg_chap ||
       * ao->neg_eap.)
       */
      orc = CONFNAK;
      PUTCHAR(CI_AUTHTYPE, nakoutp);

#if EAP_SUPPORT
      if (ao->neg_eap) {
        PUTCHAR(CILEN_SHORT, nakoutp);
        PUTSHORT(PPP_EAP, nakoutp);
      } else
#endif /* EAP_SUPPORT */
#if CHAP_SUPPORT
          if (ao->neg_chap) {
        PUTCHAR(CILEN_CHAP, nakoutp);
        PUTSHORT(PPP_CHAP, nakoutp);
        PUTCHAR(CHAP_DIGEST(ao->chap_mdtype), nakoutp);
      } else
#endif /* CHAP_SUPPORT */
#if PAP_SUPPORT
          if (1) {
        PUTCHAR(CILEN_SHORT, nakoutp);
        PUTSHORT(PPP_PAP, nakoutp);
      } else
#endif /* PAP_SUPPORT */
      {
      }
      break;

#if LQR_SUPPORT
    case CI_QUALITY:
      if (!ao->neg_lqr || cilen != CILEN_LQR) {
        orc = CONFREJ;
        break;
      }

      GETSHORT(cishort, p);
      GETLONG(cilong, p);

      /*
       * Check the protocol and the reporting period.
       * XXX When should we Nak this, and what with?
       */
      if (cishort != PPP_LQR) {
        orc = CONFNAK;
        PUTCHAR(CI_QUALITY, nakoutp);
        PUTCHAR(CILEN_LQR, nakoutp);
        PUTSHORT(PPP_LQR, nakoutp);
        PUTLONG(ao->lqr_period, nakoutp);
        break;
      }
      break;
#endif /* LQR_SUPPORT */

    case CI_MAGICNUMBER:
      if (!(ao->neg_magicnumber || go->neg_magicnumber) ||
          cilen != CILEN_LONG) {
        orc = CONFREJ;
        break;
      }
      GETLONG(cilong, p);

      /*
       * He must have a different magic number.
       */
      if (go->neg_magicnumber && cilong == go->magicnumber) {
        cilong = magic(); /* Don't put magic() inside macro! */
        orc = CONFNAK;
        PUTCHAR(CI_MAGICNUMBER, nakoutp);
        PUTCHAR(CILEN_LONG, nakoutp);
        PUTLONG(cilong, nakoutp);
        break;
      }
      ho->neg_magicnumber = 1;
      ho->magicnumber = cilong;
      break;

    case CI_PCOMPRESSION:
      if (!ao->neg_pcompression || cilen != CILEN_VOID) {
        orc = CONFREJ;
        break;
      }
      ho->neg_pcompression = 1;
      break;

    case CI_ACCOMPRESSION:
      if (!ao->neg_accompression || cilen != CILEN_VOID) {
        orc = CONFREJ;
        break;
      }
      ho->neg_accompression = 1;
      break;

#ifdef HAVE_MULTILINK
    case CI_MRRU:
      if (!ao->neg_mrru || !multilink || cilen != CILEN_SHORT) {
        orc = CONFREJ;
        break;
      }

      GETSHORT(cishort, p);
      /* possibly should insist on a minimum/maximum MRRU here */
      ho->neg_mrru = 1;
      ho->mrru = cishort;
      break;
#endif /* HAVE_MULTILINK */

    case CI_SSNHF:
      if (!ao->neg_ssnhf
#ifdef HAVE_MULTILINK
          || !multilink
#endif /* HAVE_MULTILINK */
          || cilen != CILEN_VOID) {
        orc = CONFREJ;
        break;
      }
      ho->neg_ssnhf = 1;
      break;

    case CI_EPDISC:
      if (!ao->neg_endpoint || cilen < CILEN_CHAR ||
          cilen > CILEN_CHAR + MAX_ENDP_LEN) {
        orc = CONFREJ;
        break;
      }
      GETCHAR(cichar, p);
      cilen -= CILEN_CHAR;
      ho->neg_endpoint = 1;
      ho->endpoint.class_ = cichar;
      ho->endpoint.length = cilen;
      MEMCPY(ho->endpoint.value, p, cilen);
      INCPTR(cilen, p);
      break;

    default:
      LCPDEBUG(("lcp_reqci: rcvd unknown option %d", citype));
      orc = CONFREJ;
      break;
    }

  endswitch:
    if (orc == CONFACK && /* Good CI */
        rc != CONFACK)    /*  but prior CI wasn't? */
      continue;           /* Don't send this one */

    if (orc == CONFNAK) {    /* Nak this CI? */
      if (reject_if_disagree /* Getting fed up with sending NAKs? */
          && citype != CI_MAGICNUMBER) {
        orc = CONFREJ; /* Get tough if so */
      } else {
        if (rc == CONFREJ) /* Rejecting prior CI? */
          continue;        /* Don't send this one */
        rc = CONFNAK;
      }
    }
    if (orc == CONFREJ) { /* Reject this CI */
      rc = CONFREJ;
      if (cip != rejp)            /* Need to move rejected CI? */
        MEMCPY(rejp, cip, cilen); /* Move it */
      INCPTR(cilen, rejp);        /* Update output pointer */
    }
  }

  /*
   * If we wanted to send additional NAKs (for unsent CIs), the
   * code would go here.  The extra NAKs would go at *nakoutp.
   * At present there are no cases where we want to ask the
   * peer to negotiate an option.
   */

  switch (rc) {
  case CONFACK:
    *lenp = next - inp;
    break;
  case CONFNAK:
    /*
     * Copy the Nak'd options from the nak buffer to the caller's buffer.
     */
    *lenp = nakoutp - (u_char *)nakp->payload;
    MEMCPY(inp, nakp->payload, *lenp);
    break;
  case CONFREJ:
    *lenp = rejp - inp;
    break;
  default:
    break;
  }

  pbuf_free(nakp);
  LCPDEBUG(("lcp_reqci: returning CONF%s.", CODENAME(rc)));
  return (rc); /* Return final code */
}

/*
 * lcp_up - LCP has come UP.
 */
static void lcp_up(fsm *f) {
  ppp_pcb *pcb = f->pcb;
  lcp_options *wo = &pcb->lcp_wantoptions;
  lcp_options *ho = &pcb->lcp_hisoptions;
  lcp_options *go = &pcb->lcp_gotoptions;
  lcp_options *ao = &pcb->lcp_allowoptions;
  int mtu, mru;

  if (!go->neg_magicnumber)
    go->magicnumber = 0;
  if (!ho->neg_magicnumber)
    ho->magicnumber = 0;

  /*
   * Set our MTU to the smaller of the MTU we wanted and
   * the MRU our peer wanted.  If we negotiated an MRU,
   * set our MRU to the larger of value we wanted and
   * the value we got in the negotiation.
   * Note on the MTU: the link MTU can be the MRU the peer wanted,
   * the interface MTU is set to the lowest of that, the
   * MTU we want to use, and our link MRU.
   */
  mtu = ho->neg_mru ? ho->mru : PPP_DEFMRU;
  mru = go->neg_mru ? LWIP_MAX(wo->mru, go->mru) : PPP_DEFMRU;
#ifdef HAVE_MULTILINK
  if (!(multilink && go->neg_mrru && ho->neg_mrru))
#endif /* HAVE_MULTILINK */
    ppp_netif_set_mtu(pcb, LWIP_MIN(LWIP_MIN(mtu, mru), ao->mru));
  ppp_send_config(pcb, mtu, (ho->neg_asyncmap ? ho->asyncmap : 0xffffffff),
                  ho->neg_pcompression, ho->neg_accompression);
  ppp_recv_config(pcb, mru,
                  (pcb->settings.lax_recv ? 0
                   : go->neg_asyncmap     ? go->asyncmap
                                          : 0xffffffff),
                  go->neg_pcompression, go->neg_accompression);

  if (ho->neg_mru)
    pcb->peer_mru = ho->mru;

  lcp_echo_lowerup(f->pcb); /* Enable echo messages */

  link_established(pcb);
}

/*
 * lcp_down - LCP has gone DOWN.
 *
 * Alert other protocols.
 */
static void lcp_down(fsm *f) {
  ppp_pcb *pcb = f->pcb;
  lcp_options *go = &pcb->lcp_gotoptions;

  lcp_echo_lowerdown(f->pcb);

  link_down(pcb);

  ppp_send_config(pcb, PPP_DEFMRU, 0xffffffff, 0, 0);
  ppp_recv_config(pcb, PPP_DEFMRU,
                  (go->neg_asyncmap ? go->asyncmap : 0xffffffff),
                  go->neg_pcompression, go->neg_accompression);
  pcb->peer_mru = PPP_DEFMRU;
}

/*
 * lcp_starting - LCP needs the lower layer up.
 */
static void lcp_starting(fsm *f) {
  ppp_pcb *pcb = f->pcb;
  link_required(pcb);
}

/*
 * lcp_finished - LCP has finished with the lower layer.
 */
static void lcp_finished(fsm *f) {
  ppp_pcb *pcb = f->pcb;
  link_terminated(pcb);
}

#if PRINTPKT_SUPPORT
/*
 * lcp_printpkt - print the contents of an LCP packet.
 */
static const char *const lcp_codenames[] = {
    "ConfReq", "ConfAck", "ConfNak", "ConfRej", "TermReq", "TermAck", "CodeRej",
    "ProtRej", "EchoReq", "EchoRep", "DiscReq", "Ident",   "TimeRem"};

static int lcp_printpkt(const u_char *p, int plen,
                        void (*printer)(void *, const char *, ...), void *arg) {
  int code, id, len, olen, i;
  const u_char *pstart, *optend;
  u_short cishort;
  u32_t cilong;

  if (plen < HEADERLEN)
    return 0;
  pstart = p;
  GETCHAR(code, p);
  GETCHAR(id, p);
  GETSHORT(len, p);
  if (len < HEADERLEN || len > plen)
    return 0;

  if (code >= 1 && code <= (int)LWIP_ARRAYSIZE(lcp_codenames))
    printer(arg, " %s", lcp_codenames[code - 1]);
  else
    printer(arg, " code=0x%x", code);
  printer(arg, " id=0x%x", id);
  len -= HEADERLEN;
  switch (code) {
  case CONFREQ:
  case CONFACK:
  case CONFNAK:
  case CONFREJ:
    /* print option list */
    while (len >= 2) {
      GETCHAR(code, p);
      GETCHAR(olen, p);
      p -= 2;
      if (olen < 2 || olen > len) {
        break;
      }
      printer(arg, " <");
      len -= olen;
      optend = p + olen;
      switch (code) {
      case CI_MRU:
        if (olen == CILEN_SHORT) {
          p += 2;
          GETSHORT(cishort, p);
          printer(arg, "mru %d", cishort);
        }
        break;
      case CI_ASYNCMAP:
        if (olen == CILEN_LONG) {
          p += 2;
          GETLONG(cilong, p);
          printer(arg, "asyncmap 0x%x", cilong);
        }
        break;
      case CI_AUTHTYPE:
        if (olen >= CILEN_SHORT) {
          p += 2;
          printer(arg, "auth ");
          GETSHORT(cishort, p);
          switch (cishort) {
#if PAP_SUPPORT
          case PPP_PAP:
            printer(arg, "pap");
            break;
#endif /* PAP_SUPPORT */
#if CHAP_SUPPORT
          case PPP_CHAP:
            printer(arg, "chap");
            if (p < optend) {
              switch (*p) {
              case CHAP_MD5:
                printer(arg, " MD5");
                ++p;
                break;
#if MSCHAP_SUPPORT
              case CHAP_MICROSOFT:
                printer(arg, " MS");
                ++p;
                break;

              case CHAP_MICROSOFT_V2:
                printer(arg, " MS-v2");
                ++p;
                break;
#endif /* MSCHAP_SUPPORT */
              default:
                break;
              }
            }
            break;
#endif /* CHAP_SUPPORT */
#if EAP_SUPPORT
          case PPP_EAP:
            printer(arg, "eap");
            break;
#endif /* EAP_SUPPORT */
          default:
            printer(arg, "0x%x", cishort);
          }
        }
        break;
#if LQR_SUPPORT
      case CI_QUALITY:
        if (olen >= CILEN_SHORT) {
          p += 2;
          printer(arg, "quality ");
          GETSHORT(cishort, p);
          switch (cishort) {
          case PPP_LQR:
            printer(arg, "lqr");
            break;
          default:
            printer(arg, "0x%x", cishort);
          }
        }
        break;
#endif /* LQR_SUPPORT */
      case CI_CALLBACK:
        if (olen >= CILEN_CHAR) {
          p += 2;
          printer(arg, "callback ");
          GETCHAR(cishort, p);
          switch (cishort) {
          case CBCP_OPT:
            printer(arg, "CBCP");
            break;
          default:
            printer(arg, "0x%x", cishort);
          }
        }
        break;
      case CI_MAGICNUMBER:
        if (olen == CILEN_LONG) {
          p += 2;
          GETLONG(cilong, p);
          printer(arg, "magic 0x%x", cilong);
        }
        break;
      case CI_PCOMPRESSION:
        if (olen == CILEN_VOID) {
          p += 2;
          printer(arg, "pcomp");
        }
        break;
      case CI_ACCOMPRESSION:
        if (olen == CILEN_VOID) {
          p += 2;
          printer(arg, "accomp");
        }
        break;
      case CI_MRRU:
        if (olen == CILEN_SHORT) {
          p += 2;
          GETSHORT(cishort, p);
          printer(arg, "mrru %d", cishort);
        }
        break;
      case CI_SSNHF:
        if (olen == CILEN_VOID) {
          p += 2;
          printer(arg, "ssnhf");
        }
        break;
      case CI_EPDISC:
#ifdef HAVE_MULTILINK
        if (olen >= CILEN_CHAR) {
          struct epdisc epd;
          p += 2;
          GETCHAR(epd.class, p);
          epd.length = olen - CILEN_CHAR;
          if (epd.length > MAX_ENDP_LEN)
            epd.length = MAX_ENDP_LEN;
          if (epd.length > 0) {
            MEMCPY(epd.value, p, epd.length);
            p += epd.length;
          }
          printer(arg, "endpoint [%s]", epdisc_to_str(&epd));
        }
#else
        printer(arg, "endpoint");
#endif
        break;
      default:
        break;
      }
      while (p < optend) {
        GETCHAR(code, p);
        printer(arg, " %.2x", code);
      }
      printer(arg, ">");
    }
    break;

  case TERMACK:
  case TERMREQ:
    if (len > 0 && *p >= ' ' && *p < 0x7f) {
      printer(arg, " ");
      ppp_print_string(p, len, printer, arg);
      p += len;
      len = 0;
    }
    break;

  case ECHOREQ:
  case ECHOREP:
  case DISCREQ:
    if (len >= 4) {
      GETLONG(cilong, p);
      printer(arg, " magic=0x%x", cilong);
      len -= 4;
    }
    break;

  case IDENTIF:
  case TIMEREM:
    if (len >= 4) {
      GETLONG(cilong, p);
      printer(arg, " magic=0x%x", cilong);
      len -= 4;
    }
    if (code == TIMEREM) {
      if (len < 4)
        break;
      GETLONG(cilong, p);
      printer(arg, " seconds=%u", cilong);
      len -= 4;
    }
    if (len > 0) {
      printer(arg, " ");
      ppp_print_string(p, len, printer, arg);
      p += len;
      len = 0;
    }
    break;
  default:
    break;
  }

  /* print the rest of the bytes in the packet */
  for (i = 0; i < len && i < 32; ++i) {
    GETCHAR(code, p);
    printer(arg, " %.2x", code);
  }
  if (i < len) {
    printer(arg, " ...");
    p += len - i;
  }

  return p - pstart;
}
#endif /* PRINTPKT_SUPPORT */

/*
 * Time to shut down the link because there is nothing out there.
 */

static void LcpLinkFailure(fsm *f) {
  ppp_pcb *pcb = f->pcb;
  if (f->state == PPP_FSM_OPENED) {
    ppp_info(("No response to %d echo-requests", pcb->lcp_echos_pending));
    ppp_notice(("Serial link appears to be disconnected."));
    pcb->err_code = PPPERR_PEERDEAD;
    lcp_close(pcb, "Peer not responding");
  }
}

/*
 * Timer expired for the LCP echo requests from this process.
 */

static void LcpEchoCheck(fsm *f) {
  ppp_pcb *pcb = f->pcb;

  LcpSendEchoRequest(f);
  if (f->state != PPP_FSM_OPENED)
    return;

  /*
   * Start the timer for the next interval.
   */
  if (pcb->lcp_echo_timer_running)
    ppp_warn(("assertion lcp_echo_timer_running==0 failed"));
  TIMEOUT(LcpEchoTimeout, f, pcb->settings.lcp_echo_interval);
  pcb->lcp_echo_timer_running = 1;
}

/*
 * LcpEchoTimeout - Timer expired on the LCP echo
 */

static void LcpEchoTimeout(void *arg) {
  fsm *f = (fsm *)arg;
  ppp_pcb *pcb = f->pcb;
  if (pcb->lcp_echo_timer_running != 0) {
    pcb->lcp_echo_timer_running = 0;
    LcpEchoCheck((fsm *)arg);
  }
}

/*
 * LcpEchoReply - LCP has received a reply to the echo
 */

static void lcp_received_echo_reply(fsm *f, int id, u_char *inp, int len) {
  ppp_pcb *pcb = f->pcb;
  lcp_options *go = &pcb->lcp_gotoptions;
  u32_t magic_val;
  LWIP_UNUSED_ARG(id);

  /* Check the magic number - don't count replies from ourselves. */
  if (len < 4) {
    ppp_dbglog(("lcp: received short Echo-Reply, length %d", len));
    return;
  }
  GETLONG(magic_val, inp);
  if (go->neg_magicnumber && magic_val == go->magicnumber) {
    ppp_warn(("appear to have received our own echo-reply!"));
    return;
  }

  /* Reset the number of outstanding echo frames */
  pcb->lcp_echos_pending = 0;
}

/*
 * LcpSendEchoRequest - Send an echo request frame to the peer
 */

static void LcpSendEchoRequest(fsm *f) {
  ppp_pcb *pcb = f->pcb;
  lcp_options *go = &pcb->lcp_gotoptions;
  u32_t lcp_magic;
  u_char pkt[4], *pktp;

  /*
   * Detect the failure of the peer at this point.
   */
  if (pcb->settings.lcp_echo_fails != 0) {
    if (pcb->lcp_echos_pending >= pcb->settings.lcp_echo_fails) {
      LcpLinkFailure(f);
      pcb->lcp_echos_pending = 0;
    }
  }

#if PPP_LCP_ADAPTIVE
  /*
   * If adaptive echos have been enabled, only send the echo request if
   * no traffic was received since the last one.
   */
  if (pcb->settings.lcp_echo_adaptive) {
    static unsigned int last_pkts_in = 0;

#if PPP_STATS_SUPPORT
    update_link_stats(f->unit);
    link_stats_valid = 0;
#endif /* PPP_STATS_SUPPORT */

    if (link_stats.pkts_in != last_pkts_in) {
      last_pkts_in = link_stats.pkts_in;
      return;
    }
  }
#endif

  /*
   * Make and send the echo request frame.
   */
  if (f->state == PPP_FSM_OPENED) {
    lcp_magic = go->magicnumber;
    pktp = pkt;
    PUTLONG(lcp_magic, pktp);
    fsm_sdata(f, ECHOREQ, pcb->lcp_echo_number++, pkt, pktp - pkt);
    ++pcb->lcp_echos_pending;
  }
}

/*
 * lcp_echo_lowerup - Start the timer for the LCP frame
 */

static void lcp_echo_lowerup(ppp_pcb *pcb) {
  fsm *f = &pcb->lcp_fsm;

  /* Clear the parameters for generating echo frames */
  pcb->lcp_echos_pending = 0;
  pcb->lcp_echo_number = 0;
  pcb->lcp_echo_timer_running = 0;

  /* If a timeout interval is specified then start the timer */
  if (pcb->settings.lcp_echo_interval != 0)
    LcpEchoCheck(f);
}

/*
 * lcp_echo_lowerdown - Stop the timer for the LCP frame
 */

static void lcp_echo_lowerdown(ppp_pcb *pcb) {
  fsm *f = &pcb->lcp_fsm;

  if (pcb->lcp_echo_timer_running != 0) {
    UNTIMEOUT(LcpEchoTimeout, f);
    pcb->lcp_echo_timer_running = 0;
  }
}

#endif /* PPP_SUPPORT */
